package lumis.portal.presentation.perspective.admin;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;

import lumis.portal.presentation.core.LayoutFileMarkupResourceStream;
import lumis.portal.presentation.perspective.IPortalMode;
import lumis.util.FileUtil;
import lumis.util.log.ILogger;
import lumis.util.log.LoggerFactory;

import org.apache.wicket.MarkupContainer;
import org.apache.wicket.markup.ContainerInfo;
import org.apache.wicket.markup.MarkupResourceStream;
import org.apache.wicket.markup.parser.XmlPullParser;
import org.apache.wicket.markup.parser.XmlTag;
import org.apache.wicket.util.resource.IResourceStream;
import org.apache.wicket.util.resource.ResourceStreamNotFoundException;
import org.apache.wicket.util.value.IValueMap;

/**
 * {@link MarkupResourceStream} implementation used to tag a markup resource
 * stream as being used for the Portal Studio.
 *
 * @version $Revision: 13093 $ $Date: 2011-05-28 18:40:18 -0300 (Sat, 28 May 2011) $
 * @since 6.0.0
 */
@SuppressWarnings("serial")
public class PortalStudioMarkupResourceStream extends LayoutFileMarkupResourceStream
{
	private static final String HEAD_ENDING = "</head>";
	private static final String BODY_ENDING = "</body>";
	private static final String BODY = "<body>";
	private static final String HEAD = "<head>";
	private static final String HTML_ENDING = "</html>";
	private static final String BODY_HTML_ENDING = BODY_ENDING + HTML_ENDING;

	private static final ILogger logger = LoggerFactory.getLogger(PortalStudioMarkupResourceStream.class);
	
	/**
	 * The input stream object returned by {@link #getInputStream()}.
	 * @see #generateMarkupInputStream()
	 * @since 6.0.0
	 */
	private InputStream markupInputStream;

	/**
	 * Creates a new PortalStudioMarkupResourceStream wrapping an existing
	 * resource stream.
	 * @param resourceStream the resource stream.
	 * @since 6.0.0
	 */
	public PortalStudioMarkupResourceStream(IResourceStream resourceStream, MarkupContainer container, IPortalMode portalMode)
	{
		super(resourceStream, new ContainerInfo(container), container.getClass(), portalMode);
	}
	
	/**
	 * Generates the HTML open tag, based on the attributes of a tag.
	 * @param baseTag the tag to be based on.
	 * @return the HTML code for the HTML element opening.
	 * @since 6.0.0
	 */
	private String generateHTMLOpen(XmlTag baseTag)
	{
		StringBuilder result = new StringBuilder(200);
		result.append("<html");
		IValueMap attributes = baseTag.getAttributes();
		String pageNamespace = "page";
		for (Map.Entry<String, Object> entry: attributes.entrySet())
		{
			String name = entry.getKey();
			if (name.startsWith("xmlns:"))
			{
				String value = (String) entry.getValue();
				if ("http://www.lumis.com.br/lumisportal/xsd/layoutfile/page".equals(value))
					pageNamespace = name.substring(6);
				
				result.append(" " + name + "=\"" + value + "\"");
			}
		}
		result.append(" " + pageNamespace + ":auto=\"false\"");
		result.append(">");
		return result.toString();
	}
	
	@Override
	public void close() throws IOException
	{
		super.close();
		if (markupInputStream != null)
		{
			markupInputStream.close();
			markupInputStream = null;
		}
	}

	@Override
	public InputStream getInputStream() throws ResourceStreamNotFoundException
	{
		if (markupInputStream == null)
			markupInputStream = generateMarkupInputStream();
		return markupInputStream;
	}
	
	/**
	 * Generates the markup input stream object. Called by 
	 * {@link #getInputStream()} to generate it when necessary.
	 * @return the input stream.
	 * @throws ResourceStreamNotFoundException if the resource stream was
	 * not found.
	 * @since 6.0.0
	 */
	private InputStream generateMarkupInputStream() throws ResourceStreamNotFoundException
	{
		// read the markup resource stream into byteArray
		byte[] byteArray;
		try
		{
			InputStream inputStream = super.getInputStream();
			try
			{
				ByteArrayOutputStream baos = new ByteArrayOutputStream();
				FileUtil.copyStream(inputStream, baos);
				byteArray = baos.toByteArray();
			}
			finally
			{
				// according to super.getInputStream() definition, 
				// super.close() should be used instead of inputStream.close()
				super.close();
			}
		}
		catch (IOException e)
		{
			throw new ResourceStreamNotFoundException(e);
		}
			
		try
		{
			XmlPullParser parser = new XmlPullParser();
			parser.parse(new ByteArrayInputStream(byteArray));
			String encoding = parser.getEncoding();
			XmlTag element = (XmlTag)parser.nextTag();
			String result = new String(byteArray, encoding);

			if (element == null)
			{
				// has no element.
				// injecting html, header and body...
				return new ByteArrayInputStream(("<html>" + HEAD + HEAD_ENDING + BODY + result + BODY_HTML_ENDING).getBytes(encoding));
			}

			if (!"html".equalsIgnoreCase(element.getName()))
			{
				final String HTML = generateHTMLOpen(element);
				
				// we have an element that is not HTML...
				boolean hasHead = false;
				boolean hasBody = false;
				while (!(hasBody && hasHead) && element != null)
				{
					if ("body".equalsIgnoreCase(element.getName()))
						hasBody = true;
					else if ("head".equalsIgnoreCase(element.getName()))
						hasHead = true;

					element = (XmlTag) parser.nextTag();
				}

				if (!hasBody && !hasHead)
				{
					return new ByteArrayInputStream((HTML + HEAD + HEAD_ENDING + BODY + result + BODY_HTML_ENDING).getBytes(encoding));
				}

				if (hasBody && !hasHead)
				{
					return new ByteArrayInputStream((HTML + HEAD + HEAD_ENDING + result + HTML_ENDING).getBytes(encoding));
				}

				if (hasHead && !hasBody)
				{
					return new ByteArrayInputStream((HTML + result + BODY + BODY_HTML_ENDING).getBytes(encoding));
				}

				return new ByteArrayInputStream((HTML + result + HTML_ENDING).getBytes(encoding));
			}
			else
			{
				XmlTag secondElement = (XmlTag) parser.nextTag();
				if (secondElement != null && "body".equalsIgnoreCase(secondElement.getName()))
				{
					// include a <head></head> before the body
					result = result.substring(0, secondElement.getPos()) + HEAD + HEAD_ENDING + result.substring(secondElement.getPos());
					return new ByteArrayInputStream(result.getBytes(encoding));
				}
			}

			return new ByteArrayInputStream(byteArray);
		}
		catch (Exception e)
		{
			logger.warn("Exception processing resource stream for " + this, e);
			return new ByteArrayInputStream(byteArray);
		}
	}

}